package gcontent.record.db;

import gcontent.config.PersistentConfig;
import gcontent.config.error.ConfigEx;
import gcontent.config.persistents.node.MethodNode;
import gcontent.config.persistents.node.ObjectNode;
import gcontent.db.BaseDB;
import gcontent.db.DBParams;
import gcontent.db.DBParams.Direction;
import gcontent.entities.Content;
import gcontent.entities.content.ContentManager;
import gcontent.entities.content.error.ContentEx;
import gcontent.record.Record;
import gcontent.record.error.RecordEx;
import gcontent.rsrc.ThreadResources;
import gcontent.utils.Utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;

public class RecordDB extends BaseDB implements Record  {
	
	public enum TYPE_OBJECT {
		ANY,
		CONTENT,
		CHANNEL
	};

	protected HashMap<String, String> params = null;
	
	public void loadParameters(HashMap<String, String> params) throws RecordEx
	{	
		this.driver = params.get("driver");
		if(this.driver == null)
			throw new RecordEx("driver not defined");

		this.username = params.get("username");
		if(this.username == null)
			throw new RecordEx("username not defined");
		
		this.password = params.get("password");
		if(this.password == null)
			throw new RecordEx("password not defined");
		
		this.url = params.get("url");
		if(this.url == null)
			throw new RecordEx("url not defined");

		this.autoCommit = false;
		this.params = params;
	}
	
	

	/* get a new instance */
	public Record getInstance() throws RecordEx
	{
		RecordDB record;

		record = new RecordDB();
		record.loadParameters(this.params);
		
		return record;
	}
		
	private String buildObjProc(String proc_name, Object param_values[]) 
	{
		String ret = "";
		String place_holders = "";
		
		ret = "{call " + proc_name + "(**)}";
		
		if (param_values != null && param_values.length > 0) {
			place_holders += "?, ";
			for (int i = 0; i < param_values.length; i++) {
				place_holders += "?";
				if (i < param_values.length - 1)
					place_holders += ", ";
			}
		} else {
			place_holders += "?";
		}
		
		ret = ret.replace("**", place_holders);
		
		return ret;
	}
	
	private boolean invokeSet(Object obj, ObjectNode obj_node, String colname, Object value) throws RecordEx
	{
		MethodNode method_node;
		Method m;
		Class<?>[] params;
		
		method_node = obj_node.getMethodNode(colname);
		if(method_node == null)
			return false;
		
		if(value == null)
			return false;
		
		params = new Class[1];		
		if(value.getClass().getName().startsWith("java.sql.")) {
			params[0] = value.getClass().getSuperclass();
		} else {
			params[0] = value.getClass();
		}
		
		try { 
			m = obj.getClass().getMethod("set" + method_node.getFunction(), params);
			m.invoke(obj, value);
		} catch(Exception ex) {
			throw new RecordEx(ex);
		}
			
		return true;
	}
	
	public Object execProc(String proc_name, Object ... in_params) throws ContentEx, RecordEx
	{
		CallableStatement cstmt;
		String query;
		ResultSet res;
		Object ret_obj;
		
		cstmt = null;
		res = null;
		ret_obj = null;

		try {
			if (this.connection != null) {
				query = buildObjProc(proc_name, in_params);
				cstmt = this.connection.prepareCall(query);

				/* type object */
				cstmt.registerOutParameter(1, java.sql.Types.VARCHAR);

				/* input parameters */
				for(int i = 0; i < in_params.length; i++)
					cstmt.setObject(i + 2, in_params[i]);

				/* execute procedure */
				cstmt.execute();	
				
				if(cstmt.getObject(1) != null) {
					try {
						ret_obj = Integer.parseInt((String)cstmt.getObject(1));
					} catch(NumberFormatException num_ex) {
						ret_obj = cstmt.getObject(1);
					}
				} 			
			}
		}
		catch (SQLException sql_ex) {
			throw new RecordEx(sql_ex);
		} finally {

			if(res != null) {
				try { res.close(); } catch(SQLException ex) { ex.printStackTrace(); }
			}

			if (cstmt != null) {
				try {
					if (!cstmt.isClosed())
						cstmt.close(); 					
				} catch(SQLException ex) { ex.printStackTrace(); }
			}
		}	

		return ret_obj;			
	}

	/* get a object (content, channel) and fill  */
	public ArrayList<?> getObject(String proc_name, Object ... in_params) throws ContentEx, RecordEx
	{
		CallableStatement cstmt;
		String query;
		Class<?> class_obj;
		ArrayList<Object> ret_obj_list;
		ResultSet res;
		String ret_str;
		ObjectNode obj_node;
			
		ret_obj_list = new ArrayList<Object>();
		cstmt = null;
		res = null;

		try {
			if (this.connection != null) {
				query = buildObjProc(proc_name, in_params);
				cstmt = this.connection.prepareCall(query);

				/* type object */
				cstmt.registerOutParameter(1, java.sql.Types.VARCHAR);
	
				/* input parameters */
				for(int i = 0; i < in_params.length; i++)
					cstmt.setObject(i + 2, in_params[i]);
	
				/* execute procedure */
				cstmt.execute();

				res = cstmt.getResultSet();	
				if(res == null)
					return ret_obj_list;
				
				ResultSetMetaData rsmd = res.getMetaData();
				int n_cols = rsmd.getColumnCount();
				
				ret_str = cstmt.getObject(1).toString();
				if(ret_str.length() <= 0)
					return ret_obj_list;

				obj_node = PersistentConfig.getConf(ret_str);
				if(obj_node == null)
					throw new RecordEx("not exist persistent configuration for type " + ret_str);
	
				try { 
					class_obj = RecordDB.class.getClassLoader().loadClass(obj_node.getClassName());
				} catch(ClassNotFoundException ex) {
					throw new RecordEx("class not found : persistent type => " + ret_str + " class => " + obj_node.getClassName());
				}
				
				while(res.next()) {
					Object cur_obj;
					
					try { 
						cur_obj = class_obj.newInstance();
					} catch(Exception ex) {
						throw new RecordEx("error invoking constructor of class " + obj_node.getClassName());
					}
						
					/* mark object as cached */
					if(cur_obj instanceof Content)
						((Content)cur_obj).setCached(true);
					
					for(int i = 1; i <= n_cols; i++) {
						try { 
							invokeSet(cur_obj, obj_node, rsmd.getColumnName(i), res.getObject(i));
						} catch(Exception ex) {
							ex.printStackTrace();
						}
					}
					
					ret_obj_list.add(cur_obj);
				}
			}
		}
		catch (SQLException sql_ex) {
			throw new RecordEx(sql_ex);
		} finally {
			
			if(res != null) {
				try { res.close(); } catch(SQLException ex) { ex.printStackTrace(); }
			}
						
			if (cstmt != null) {
				try {
					if (!cstmt.isClosed())
						cstmt.close(); 					
				} catch(SQLException ex) { ex.printStackTrace(); }
			}
		}	
		
		return ret_obj_list;
	}
	
	
	public void flush() throws RecordEx
	{
		try { 
			commit();
		} catch(Exception ex) {
			throw new RecordEx(ex);
		}
	}
	
	public void restore() throws RecordEx
	{
		try { 
			rollback();
		} catch(Exception ex) {
			throw new RecordEx(ex);
		}
	}

}
